Skip to content

Conversation

@Gmin2
Copy link
Contributor

@Gmin2 Gmin2 commented Sep 4, 2025

Description

Implemented dynamic datasource removing themseleves and adding them at end blocks

Fixes #2099

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist

  • I have tested locally
  • I have performed a self review of my changes
  • Updated any relevant documentation
  • Linked to any relevant issues
  • I have added tests relevant to my changes
  • Any dependent changes have been merged and published in downstream modules
  • My code is up to date with the base branch
  • I have updated relevant changelogs. We suggest using chan

Summary by CodeRabbit

  • New Features

    • Destroy dynamic datasources by template + index and query active datasources by template.
    • Optional endBlock on datasources to mark lifecycle end and exclude destroyed sources during the same block.
    • Expose destroy/get datasource operations and datasource-param lookup to runtime/worker host functions and global bindings.
    • Indexing now tracks block height and skips datasources whose endBlock ≤ current height.
  • Tests

    • Expanded tests for destruction flows, endBlock behavior, template-scoped queries, error cases, and in-memory state consistency.

✏️ Tip: You can customize this high-level summary in your review settings.

Copy link
Collaborator

@stwiname stwiname left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is on the right track but it is flawed currently.

With dynamic datasources there can be multiple instances from the same template, the only differences being the start block and arguments. So there needs to be more information passed through to remove the correct datasource.

I think the best solution for this is to provide the ability to list dynamic datasources for a template. Then the user can select the appropriate one to remove by providing the index.

@Gmin2 Gmin2 requested a review from stwiname October 17, 2025 06:36
@Gmin2
Copy link
Contributor Author

Gmin2 commented Oct 17, 2025

@stwiname just implemented listing of dynamic datasources and to remove it by index no

async destroyDynamicDatasource(
templateName: string,
currentBlockHeight: number,
index?: number,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Index should be required, it then simplifies this code and avoids users accidentally removing all dynamic datasources.

await this.dynamicDsService.destroyDynamicDatasource(templateName, blockHeight, index);

// Mark datasources with this template for removal from current processing
filteredDataSources.forEach((fds) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The destroyed dynamic datasources should be removed from filteredDatasources, at this point it shouldn't be used anymore.

…ces should be removed from filteredDatasources
@Gmin2 Gmin2 force-pushed the dynamic-datasource branch from c580346 to 4971c3d Compare October 22, 2025 16:02
.filter(({params}) => params.templateName === templateName && params.endBlock === undefined);

return matchingDatasources.map(({globalIndex, params}, index) => ({
index,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be the global index?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes thnx for this !

Comment on lines 143 to 145
const globalIndex = this._datasourceParams.findIndex(
(p) => p.templateName === dsInfo.templateName && p.startBlock === dsInfo.startBlock && p.endBlock === undefined
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just have the index on DynamicDatasourceInfo then there's no need to filter by templateName, find the index, then find the global index.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice one, implemented in af7339f

const dsParam = (this.dynamicDsService as any)._datasourceParams?.find(
(p: any) =>
p.templateName === (fds as any).mapping?.file?.split('/').pop()?.replace('.js', '') ||
(p.startBlock === (fds as any).startBlock && p.templateName === templateName)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is sufficient, there could be multiple instances of a dynamic datasource with the same startBlock but different args.

Comment on lines 141 to 142
filteredDataSources.length = 0;
filteredDataSources.push(...updatedFilteredDataSources);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just filteredDatasources = updatedFilteredDataSources?

@coderabbitai
Copy link

coderabbitai bot commented Oct 23, 2025

Walkthrough

Adds lifecycle controls for dynamic datasources: datasources gain optional endBlock; services, indexer manager, worker host/VM bindings, and types expose destroy/get operations by template/index; tests expanded to validate destruction, endBlock propagation, and in-memory/persisted metadata consistency.

Changes

Cohort / File(s) Change Summary
Type Definitions
packages/types-core/src/interfaces.ts
Added DynamicDatasourceDestructor, DynamicDatasourceGetter, and DynamicDatasourceInfo types/interfaces describing destructor signature and datasource metadata (index, templateName, startBlock, optional endBlock, args).
Global API Exports
packages/types-core/src/global.ts
Exposed global VM handler bindings destroyDynamicDatasource and getDynamicDatasources (typed to new interfaces).
Core Dynamic DS Service
packages/node-core/src/indexer/dynamic-ds.service.ts
Added optional endBlock?: number to DatasourceParams; extended public API with destroyDynamicDatasource, getDynamicDatasourcesByTemplate, getDatasourceParamByIndex; updated getTemplate/getDatasource to accept/propagate endBlock; implemented destruction flow that sets endBlock, updates in-memory state, persists metadata, and validates template/index consistency.
Indexer VM Sandbox
packages/node-core/src/indexer/indexer.manager.ts
Injected VM functions getDynamicDatasources(templateName) and destroyDynamicDatasource(templateName, blockHeight, index) bound to dynamic DS service; destruction updates datasource endBlock in filteredDataSources so subsequent handlers in the same block respect endBlock.
Worker Dynamic DS Support
packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts
Extended HostDynamicDS interface and hostDynamicDsKeys with dynamicDsDestroyDynamicDatasource, dynamicDsGetDynamicDatasourcesByTemplate, and dynamicDsGetDatasourceParamByIndex; added corresponding methods on WorkerDynamicDsService and bound them in dynamicDsHostFunctions.
Node Indexer Flow
packages/node/src/indexer/indexer.manager.ts
Threaded blockHeight into indexContent calls and added guard to skip processing for datasources with endBlock defined and <= current blockHeight.
Tests
packages/node-core/src/indexer/dynamic-ds.service.spec.ts
Expanded test project templates to include a second template and testParamOther; updated test helper getTemplate to accept endBlock; added tests covering destruction (valid, non-existent, already-destroyed), creation after destruction, resets, endBlock propagation, multi-template indexing, and in-memory _datasourceParams/_datasources consistency.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Handler as Handler
    participant VM as VM Sandbox
    participant Manager as Indexer Manager
    participant Service as DynamicDsService
    participant DB as Metadata Store

    Handler->>VM: destroyDynamicDatasource(templateName, index)
    VM->>Manager: destroyDynamicDatasource(templateName, blockHeight, index)
    Manager->>Service: destroyDynamicDatasource(templateName, blockHeight, index)
    Service->>Service: getDatasourceParamByIndex(index)
    alt found & active
        Service->>Service: set param.endBlock = blockHeight
        Service->>Service: update in-memory datasource (endBlock)
        Service->>DB: persist updated params (metadata)
        Service-->>Manager: success
        Manager->>Manager: re-filter filteredDataSources (apply endBlock)
        Manager-->>VM: ack
        VM-->>Handler: continue (datasource excluded for future handlers)
    else not found / already destroyed
        Service-->>Manager: throw error
        Manager-->>VM: propagate error
        VM-->>Handler: error returned
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review synchronization between updating _datasourceParams and _datasources and persistence steps.
  • Validate index vs template-name checks and error messages for off-by-one or template-local/global index mismatches.
  • Confirm VM/worker host binding names and cross-thread delegation correctness.
  • Check indexContent gating logic to ensure datasources with endBlock are skipped exactly when intended.
  • Inspect tests for coverage correctness and for any brittle assumptions about ordering/indices.

Poem

🐇 I nibble code and mark the end,

a little hop, a tidy friend.
Templates closed when blocks align,
metadata neat, indices fine.
Hooray — the lifecycle's now in line!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues check ❓ Inconclusive The PR implements core requirements from #2099: destructor injection, endBlock support, and methods to list/query dynamic datasources. However, documentation, codegen updates, and example project are not evidenced in code changes. Confirm that documentation updates, codegen changes, and example project have been addressed in separate PRs or branches, or document why they were deferred.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: allowing dynamic datasources to remove themselves and be added at end blocks.
Out of Scope Changes check ✅ Passed All code changes directly support the feature requirements: new methods for destroying datasources, endBlock handling, interface extensions, and VM injection for handler access.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87dca65 and 2d35591.

📒 Files selected for processing (2)
  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts (6 hunks)
  • packages/node-core/src/indexer/indexer.manager.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript throughout with strict configuration in the monorepo
Use Ethers.js for Ethereum integration

Files:

  • packages/node-core/src/indexer/indexer.manager.ts
  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run ESLint with TypeScript support, Prettier, and various plugins across all TypeScript files using configured husky pre-commit hooks and lint-staged

Files:

  • packages/node-core/src/indexer/indexer.manager.ts
  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use custom Jest configuration with module name mapping for workspace packages, enforce UTC timezone for tests, and handle Polkadot packages in transform ignore patterns

Files:

  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts
🧬 Code graph analysis (1)
packages/node-core/src/indexer/dynamic-ds.service.spec.ts (1)
packages/node-core/src/indexer/dynamic-ds.service.ts (1)
  • DatasourceParams (18-23)
🔇 Additional comments (12)
packages/node-core/src/indexer/indexer.manager.ts (2)

119-122: LGTM: getDynamicDatasources injection is clean.

The VM freeze correctly exposes the service method to retrieve datasources by template name.


124-129: Critical issue: Destroyed datasource remains in filteredDataSources for current block.

Setting endBlock doesn't remove the datasource from the filteredDataSources array during the current block's processing. The comment "child class implementation should check ds.endBlock" pushes responsibility to subclasses, which is fragile and error-prone.

Issues:

  1. indexBlockData (line 101) iterates over filteredDataSources, and setting endBlock doesn't affect the ongoing iteration.
  2. Subsequent handlers in the same block will still process the destroyed datasource unless they manually check endBlock.
  3. filterDataSources (line 154) uses >= nextProcessingHeight, so datasources with endBlock === blockHeight are included.

This issue was previously flagged in past reviews but appears unresolved.

Solution: Remove the destroyed datasource from filteredDataSources in-place to prevent subsequent handlers in this block from processing it.

packages/node-core/src/indexer/dynamic-ds.service.spec.ts (10)

18-19: LGTM: getTemplate signature updated for endBlock support.

The test helper correctly exposes the protected method with the new endBlock parameter.


27-27: LGTM: Additional test constant for multi-template scenarios.

The testParamOther constant enables testing cross-template operations.


74-87: LGTM: Basic destruction test is comprehensive.

The test correctly verifies that destroying a datasource sets its endBlock and leaves other datasources unaffected.


89-106: LGTM: Error handling tests cover key edge cases.

Both tests validate appropriate error messages for invalid destruction attempts.


108-135: LGTM: Lifecycle test validates destroy-then-create flow.

The test correctly verifies that destroyed datasources remain in history while new ones are added.


150-168: LGTM: Reset behavior correctly preserves destruction state.

The test validates that reset operations don't undo previous destructions.


194-216: LGTM: Metadata and persistence tests validate endBlock handling.

Both tests correctly verify that endBlock values persist and load from metadata.


231-237: LGTM: Template creation with endBlock is tested.

The test correctly validates the new endBlock parameter in template creation.


239-260: LGTM: Edge case tests for initialization and multi-template scenarios.

Tests correctly validate index-based destruction across templates and initialization requirements.


262-457: LGTM: Comprehensive test coverage for dynamic datasource lifecycle.

The test suites thoroughly validate:

  • Datasource retrieval by template with proper filtering of destroyed datasources
  • Index-based destruction with global index handling
  • Error conditions: out of bounds, negative indices, template mismatches, already destroyed
  • In-memory state consistency across operations
  • Cross-template operations
  • Metadata persistence

The test coverage exceeds the ≥80% requirement from the PR objectives and validates all critical paths.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/node-core/src/indexer/indexer.manager.ts (1)

140-149: Consider a more robust comparison for args.

The current implementation uses JSON.stringify to compare datasource arguments (lines 144-145), which can produce false negatives if object keys are in different orders. Additionally, the fallback chain for retrieving args from processor.options || options is fragile and may not correctly locate args across different datasource types.

Consider using a deep-equality utility (e.g., lodash's isEqual) or normalizing the comparison by sorting object keys before stringification.

Apply this approach:

+import {isEqual} from 'lodash';

  // Filter out the destroyed datasource by matching startBlock and args
  // Note: Reassigning filteredDataSources is intentional - subsequent handlers
  // within the same block will see the updated filtered list
  filteredDataSources = filteredDataSources.filter((fds) => {
    const fdsStartBlock = (fds as any).startBlock;
    // For custom datasources, args are stored in processor.options
    // For runtime datasources, they may be stored differently
-   const fdsArgs = JSON.stringify((fds as any).processor?.options || (fds as any).options || {});
-   const paramArgs = JSON.stringify(destroyedDsParam.args || {});
+   const fdsArgs = (fds as any).processor?.options || (fds as any).options || {};
+   const paramArgs = destroyedDsParam.args || {};

    // Keep datasource if it doesn't match the destroyed one
-   return !(fdsStartBlock === destroyedDsParam.startBlock && fdsArgs === paramArgs);
+   return !(fdsStartBlock === destroyedDsParam.startBlock && isEqual(fdsArgs, paramArgs));
  });
packages/node-core/src/indexer/dynamic-ds.service.ts (1)

137-183: Consider type-safe endBlock assignment.

The destruction logic is comprehensive with good validation. However, line 178 uses as any to assign endBlock to the datasource object, which bypasses TypeScript's type safety.

If the datasource type doesn't include endBlock, consider:

  1. Updating the base datasource types to include an optional endBlock property, or
  2. Documenting why the type assertion is necessary (e.g., if datasource types are dynamically extended at runtime)

This would improve type safety and make the code's intent clearer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acc14d4 and cd60d99.

📒 Files selected for processing (6)
  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts (6 hunks)
  • packages/node-core/src/indexer/dynamic-ds.service.ts (4 hunks)
  • packages/node-core/src/indexer/indexer.manager.ts (2 hunks)
  • packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (2 hunks)
  • packages/types-core/src/global.ts (2 hunks)
  • packages/types-core/src/interfaces.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
packages/types-core/src/global.ts (3)
packages/node-core/src/indexer/dynamic-ds.service.ts (2)
  • destroyDynamicDatasource (137-183)
  • getDynamicDatasources (186-196)
packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (2)
  • destroyDynamicDatasource (40-42)
  • getDynamicDatasources (44-46)
packages/types-core/src/interfaces.ts (2)
  • DynamicDatasourceDestructor (5-5)
  • DynamicDatasourceGetter (26-26)
packages/node-core/src/indexer/indexer.manager.ts (1)
packages/node-core/logger.js (1)
  • logger (5-5)
packages/node-core/src/indexer/dynamic-ds.service.ts (1)
packages/types-core/src/interfaces.ts (1)
  • DynamicDatasourceInfo (10-24)
packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (2)
packages/node-core/src/indexer/dynamic-ds.service.ts (2)
  • DatasourceParams (18-23)
  • IDynamicDsService (25-32)
packages/types-core/src/interfaces.ts (1)
  • DynamicDatasourceInfo (10-24)
🔇 Additional comments (9)
packages/node-core/src/indexer/indexer.manager.ts (2)

92-92: LGTM: Correct mutability change.

Changing from const to let is necessary to support the runtime filtering of destroyed datasources at line 140.


119-122: LGTM: Clean injection of getDynamicDatasources.

The function properly delegates to the dynamic datasource service and follows the established pattern for VM injections.

packages/types-core/src/global.ts (1)

5-5: LGTM: Clean addition of global bindings.

The new imports and global declarations properly expose the dynamic datasource destruction and querying APIs, aligning with the interfaces defined in interfaces.ts.

Also applies to: 15-16

packages/types-core/src/interfaces.ts (1)

5-26: LGTM: Well-designed public API types.

The new type definitions are clear, well-documented, and provide a clean public API surface for dynamic datasource lifecycle management. The DynamicDatasourceInfo interface appropriately includes the global index, making destruction straightforward.

packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (1)

6-21: LGTM: Proper worker-host bridge implementation.

The new methods correctly extend the worker-host communication bridge for dynamic datasource destruction and template-based querying, maintaining consistency with the existing delegation pattern.

Also applies to: 40-50, 56-59

packages/node-core/src/indexer/dynamic-ds.service.ts (3)

98-122: LGTM: Clean template-based datasource querying.

The implementation correctly filters datasources by template name and excludes destroyed ones. Using the global index in the returned DynamicDatasourceInfo objects is the right approach for enabling destruction by index.


124-135: LGTM: Simple and safe bounds-checked getter.

The implementation provides straightforward access to datasource parameters by global index with appropriate bounds checking.


213-219: LGTM: Template construction properly includes endBlock.

The signature update and implementation correctly handle the optional endBlock parameter, ensuring it's properly propagated to constructed datasources.

packages/node-core/src/indexer/dynamic-ds.service.spec.ts (1)

18-20: LGTM: Comprehensive test coverage for new functionality.

The test suite thoroughly covers:

  • Happy path: creation, destruction, and querying
  • Error cases: bounds checking, already destroyed, template mismatches
  • Edge cases: global indexing after multiple destructions, multiple templates, reset behavior
  • Integration: metadata persistence and in-memory consistency

This meets the PR objective of achieving at least 80% test coverage on new code.

Also applies to: 27-27, 44-44, 74-438

@Gmin2 Gmin2 requested a review from stwiname October 23, 2025 11:03
Comment on lines 131 to 134
if (!this._datasourceParams || index < 0 || index >= this._datasourceParams.length) {
return undefined;
}
return this._datasourceParams[index];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!this._datasourceParams || index < 0 || index >= this._datasourceParams.length) {
return undefined;
}
return this._datasourceParams[index];
return this._datasourceParams?.[index];

Comment on lines 147 to 151
// Validate the global index is within bounds
if (index < 0 || index >= this._datasourceParams.length) {
throw new Error(
`Index ${index} is out of bounds. There are ${this._datasourceParams.length} datasource(s) in total`
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be removed and instead check that dsParam below is not undefined.

Comment on lines 142 to 145
// For custom datasources, args are stored in processor.options
// For runtime datasources, they may be stored differently
const fdsArgs = JSON.stringify((fds as any).processor?.options || (fds as any).options || {});
const paramArgs = JSON.stringify(destroyedDsParam.args || {});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a better option might be to call filterDataSources again on the filtered datasources.

// Note: Reassigning filteredDataSources is intentional - subsequent handlers
// within the same block will see the updated filtered list
filteredDataSources = filteredDataSources.filter((fds) => {
const fdsStartBlock = (fds as any).startBlock;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to remove this any cast

@Gmin2 Gmin2 requested a review from stwiname October 28, 2025 09:45
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/node-core/src/indexer/indexer.manager.ts (1)

119-123: Optional: return immutable copies from getDynamicDatasources.

Handlers could accidentally mutate returned info objects. Consider returning Object.freeze()-d copies to harden the VM API. No behavior change required.

packages/node-core/src/indexer/dynamic-ds.service.spec.ts (1)

262-333: Add tests for identical startBlock with different args + param lookup.

  • Add a case with two 'Test' datasources sharing startBlock but different args; destroy by global index should affect only the targeted one.
  • Add a small unit for getDatasourceParamByIndex (valid index, OOB).

Example:

it('destroys by global index when startBlock collides but args differ', async () => {
  const a = {templateName:'Test', startBlock: 10, args:{id: 'A'}};
  const b = {templateName:'Test', startBlock: 10, args:{id: 'B'}};
  const meta = mockMetadata([a, b]);
  await service.init(meta);
  await service.destroyDynamicDatasource('Test', 20, 1); // destroy 'B'
  const params = (service as any)._datasourceParams;
  expect(params[0]).toEqual(a);
  expect(params[1]).toEqual({...b, endBlock: 20});
});

it('getDatasourceParamByIndex returns undefined when OOB', async () => {
  const meta = mockMetadata([testParam1]);
  await service.init(meta);
  expect(service.getDatasourceParamByIndex(5)).toBeUndefined();
  expect(service.getDatasourceParamByIndex(0)).toEqual(testParam1);
});
packages/node-core/src/indexer/dynamic-ds.service.ts (1)

134-181: Add a guard to prevent destruction before startBlock.

Defensive check avoids setting endBlock earlier than the datasource starts.

Apply this diff:

   // Validate it matches the template name and is not already destroyed
   if (dsParam.templateName !== templateName) {
     throw new Error(
       `Datasource at index ${index} has template name "${dsParam.templateName}", not "${templateName}"`
     );
   }

   if (dsParam.endBlock !== undefined) {
     throw new Error(`Dynamic datasource at index ${index} is already destroyed`);
   }
+
+  // Prevent destroying before the datasource has started
+  if (currentBlockHeight < dsParam.startBlock) {
+    throw new Error(
+      `Cannot destroy datasource at index ${index} before its startBlock (${dsParam.startBlock})`
+    );
+  }

Optionally, expose a helper for the manager to remove items in-place without array scans:

+  /** Returns the DS object by global index (active or destroyed). */
+  getDatasourceByIndex(index: number): DS | undefined {
+    return this._datasources?.[index];
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd60d99 and 2905c79.

📒 Files selected for processing (3)
  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts (6 hunks)
  • packages/node-core/src/indexer/dynamic-ds.service.ts (4 hunks)
  • packages/node-core/src/indexer/indexer.manager.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/node-core/src/indexer/dynamic-ds.service.ts (1)
packages/types-core/src/interfaces.ts (1)
  • DynamicDatasourceInfo (10-24)
🔇 Additional comments (6)
packages/node-core/src/indexer/indexer.manager.ts (1)

92-92: LGTM on making filteredDataSources mutable.

Using let here is required for subsequent in-block updates.

packages/node-core/src/indexer/dynamic-ds.service.spec.ts (2)

18-20: LGTM: exposing getTemplate for tests.

Keeps production visibility intact while enabling endBlock assertions.


74-87: Destroy lifecycle tests look solid.

Good coverage of success paths, OOB, double-destroy, cross-template, and metadata sync.

Also applies to: 194-217, 334-437

packages/node-core/src/indexer/dynamic-ds.service.ts (3)

5-5: LGTM: types import and endBlock in params.

endBlock on DatasourceParams and DynamicDatasourceInfo usage align with the new lifecycle.

Also applies to: 22-22


98-122: Getter by template with global indices looks good.

Clear contract: returns active items and exposes global index for destruction.


210-217: LGTM: endBlock propagation into templates and ds objects.

Ensures destroyed state is reflected in memory and persisted via updateDynamicDs.

Also applies to: 219-221

Comment on lines 124 to 144
// Inject function to destroy ds into vm
vm.freeze(async (templateName: string, index: number) => {
await this.dynamicDsService.destroyDynamicDatasource(templateName, blockHeight, index);

// Re-filter datasources to exclude the destroyed one
// The destroyed datasource now has endBlock set, so filterDataSources will exclude it
// Note: Reassigning filteredDataSources is intentional - subsequent handlers
// within the same block will see the updated filtered list
filteredDataSources = this.filterDataSources(blockHeight, filteredDataSources);
}, 'destroyDynamicDatasource');

Copy link

@coderabbitai coderabbitai bot Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

In-block destroy isn’t effective: reassign doesn’t affect current iteration; filter logic won’t exclude endBlock === current block.

  • Reassigning filteredDataSources won’t change the array that indexBlockData is iterating.
  • filterDataSources includes ds where endBlock >= height, so a destroyed ds with endBlock === blockHeight will still be included for this block.

Fix by removing the destroyed DS from filteredDataSources in place using object identity.

Apply this diff:

         vm.freeze(async (templateName: string, index: number) => {
           await this.dynamicDsService.destroyDynamicDatasource(templateName, blockHeight, index);

-          // Re-filter datasources to exclude the destroyed one
-          // The destroyed datasource now has endBlock set, so filterDataSources will exclude it
-          // Note: Reassigning filteredDataSources is intentional - subsequent handlers
-          // within the same block will see the updated filtered list
-          filteredDataSources = this.filterDataSources(blockHeight, filteredDataSources);
+          // Remove the destroyed DS from the current list IN PLACE so the rest of this block
+          // no longer schedules it. EndBlock handles exclusion from subsequent blocks.
+          const allDynamic = await this.dynamicDsService.getDynamicDatasources();
+          const destroyed = allDynamic[index] as DS | undefined;
+          if (destroyed) {
+            const pos = filteredDataSources.indexOf(destroyed);
+            if (pos !== -1) filteredDataSources.splice(pos, 1);
+          }
         }, 'destroyDynamicDatasource');

Run this script to confirm indexBlockData iterates the original array (so reassigning the variable is ineffective):


🏁 Script executed:

#!/bin/bash
# Look for indexBlockData implementations and how 'dataSources' is iterated
rg -nP -C3 '(?:async\s+)?indexBlockData\s*\(.*dataSources\s*:\s*[^\)]*\)' --type ts
rg -nP -C3 '\bfor\s*\(\s*const\s+\w+\s+of\s+dataSources\b|\bdataSources\.forEach\(' --type ts

Length of output: 2595


I need to gather more information about the indexing flow. Let me search for the complete context.

I need to examine the indexBlockData implementation and the filterDataSources logic to verify the claims.

Let me search directly in the codebase for the relevant implementations.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find indexBlockData implementation with context
rg -nA20 'indexBlockData' packages/node-core/src/indexer/indexer.manager.ts --type ts

Length of output: 2063


🏁 Script executed:

#!/bin/bash
# Find filterDataSources implementation with endBlock logic
rg -nA15 'filterDataSources' packages/node-core/src/indexer/indexer.manager.ts --type ts

Length of output: 2146


🏁 Script executed:

#!/bin/bash
# Get the full context around lines 100-160 in indexer.manager.ts
sed -n '100,160p' packages/node-core/src/indexer/indexer.manager.ts

Length of output: 2380


Let me check for all implementations of indexBlockData to ensure the pattern is consistent:


🏁 Script executed:

#!/bin/bash
# Find all indexBlockData implementations
rg -nB2 -A10 'indexBlockData\s*\(' packages/ --type ts -g '!*.spec.ts' | grep -A10 'for\|forEach'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Verify when vm.freeze callbacks are executed - search for where handlers use injected functions
rg -nC5 'destroyDynamicDatasource|createDynamicDatasource' packages/ --type ts -g '!*.spec.ts' | head -60

Length of output: 4941


The review comment is verified as correct and identifies a real bug.

Based on my analysis of the codebase:

  1. Reassignment ineffectiveness confirmed: The indexBlockData method iterates over filteredDataSources with for...of (line 193). Reassigning the variable inside the callback doesn't affect the ongoing iteration—only in-place modifications (like push at line 115) would work during iteration.

  2. Filter logic issue confirmed: The filterDataSources condition at line 158 is (ds.endBlock ?? Number.MAX_SAFE_INTEGER) >= nextProcessingHeight. When a DS is destroyed with endBlock === blockHeight, this condition evaluates to true, so the destroyed DS is NOT excluded for the current block.

  3. Fix is sound: Using splice() with indexOf() to remove the destroyed DS in-place would correctly remove it from the array during iteration, affecting subsequent handler executions in the same block.

🤖 Prompt for AI Agents
In packages/node-core/src/indexer/indexer.manager.ts around lines 124-134, the
current callback reassigns filteredDataSources after destroying a dynamic
datasource which doesn't affect an ongoing for...of iteration; replace that
reassignment with an in-place removal: locate the destroyed datasource in
filteredDataSources by matching templateName and index (or the datasource
identifier used elsewhere) and call filteredDataSources.splice(foundIndex, 1) so
subsequent handlers in the same block see it removed; do not reassign the
variable. Also verify and, if necessary, fix filterDataSources' condition so a
datasource with endBlock === blockHeight is excluded from current processing
(e.g., use > nextProcessingHeight or adjust the comparison) to avoid future
inconsistencies.

✅ Addressed in commit 1382702

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Gmin2 it would be good to address this and to have a test that confirms the correct behaviour

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@Gmin2 Gmin2 force-pushed the dynamic-datasource branch from 2905c79 to 1382702 Compare October 31, 2025 08:41
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/node-core/src/indexer/indexer.manager.ts (1)

124-143: Confirmed: The matching logic is redundant and fragile. Use object reference tracking instead.

The review comment correctly identifies a real issue. The DynamicDsService maintains synchronized _datasources and _datasourceParams arrays indexed by global index. When destroyDynamicDatasource(templateName, blockHeight, index) is called, the index parameter directly identifies the datasource via this._datasourceParams[index].

The current code at lines 132–138 unnecessarily re-matches datasources in filteredDataSources using property comparison (startBlock + serialized options/args), which:

  1. Is redundant: The index already identifies the exact datasource
  2. Fails with property mismatch: Compares (fds as any).options || (fds as any).processor?.options against args — these are distinct properties with unclear relationship
  3. Is brittle: Multiple datasources could share the same startBlock and args; JSON.stringify is order-dependent

Solution: Store the created datasource reference when adding to filteredDataSources, then use it for removal:

        // Inject function to create ds into vm
        vm.freeze(async (templateName: string, args?: Record<string, unknown>) => {
          const newDs = await this.dynamicDsService.createDynamicDatasource({
            templateName,
            args,
            startBlock: blockHeight,
          });
          // Push the newly created dynamic ds to be processed this block on any future extrinsics/events
          filteredDataSources.push(newDs);
          dynamicDsCreated = true;
        }, 'createDynamicDatasource');

        // Inject function to destroy ds into vm
-       vm.freeze(async (templateName: string, index: number) => {
+       const createdDsMap = new Map<number, DS>();
+       vm.freeze(async (templateName: string, index: number) => {
          await this.dynamicDsService.destroyDynamicDatasource(templateName, blockHeight, index);

-         // Remove the destroyed datasource from the current processing array
-         // Find the datasource by matching the global index stored in the service
-         const destroyedDsParam = this.dynamicDsService.getDatasourceParamByIndex(index);
-         if (destroyedDsParam) {
-           const dsIndex = filteredDataSources.findIndex((fds) => {
-             return (
-               fds.startBlock === destroyedDsParam.startBlock &&
-               JSON.stringify((fds as any).options || (fds as any).processor?.options || {}) ===
-                 JSON.stringify(destroyedDsParam.args || {})
-             );
-           });
-           if (dsIndex !== -1) {
-             filteredDataSources.splice(dsIndex, 1);
-           }
-         }
+         // Remove by reference (stored during creation)
+         const destroyed = createdDsMap.get(index);
+         if (destroyed) {
+           const pos = filteredDataSources.indexOf(destroyed);
+           if (pos !== -1) filteredDataSources.splice(pos, 1);
+         }
        }, 'destroyDynamicDatasource');

        // Store reference mapping during creation
-       vm.freeze(async (templateName: string, args?: Record<string, unknown>) => {
+       const originalCreate = async (templateName: string, args?: Record<string, unknown>) => {
          const newDs = await this.dynamicDsService.createDynamicDatasource({
            templateName,
            args,
            startBlock: blockHeight,
          });
          filteredDataSources.push(newDs);
+         const currentIndex = this.dynamicDsService.dynamicDatasources.length - 1;
+         createdDsMap.set(currentIndex, newDs);
          dynamicDsCreated = true;
-       }, 'createDynamicDatasource');
+       };
+       vm.freeze(originalCreate, 'createDynamicDatasource');
🧹 Nitpick comments (1)
packages/node-core/src/indexer/dynamic-ds.service.ts (1)

18-23: Consider adding JSDoc for the endBlock field.

While the implementation is correct, adding documentation would help users understand when and how endBlock is set.

 export interface DatasourceParams {
   templateName: string;
   args?: Record<string, unknown>;
   startBlock: number;
+  /** Block height where this datasource stops processing (set when destroyed) */
   endBlock?: number;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2905c79 and d2a9845.

📒 Files selected for processing (6)
  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts (6 hunks)
  • packages/node-core/src/indexer/dynamic-ds.service.ts (4 hunks)
  • packages/node-core/src/indexer/indexer.manager.ts (1 hunks)
  • packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (2 hunks)
  • packages/types-core/src/global.ts (2 hunks)
  • packages/types-core/src/interfaces.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/types-core/src/interfaces.ts
🧰 Additional context used
🧬 Code graph analysis (3)
packages/node-core/src/indexer/dynamic-ds.service.ts (1)
packages/types-core/src/interfaces.ts (1)
  • DynamicDatasourceInfo (10-24)
packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (2)
packages/node-core/src/indexer/dynamic-ds.service.ts (2)
  • DatasourceParams (18-23)
  • IDynamicDsService (25-32)
packages/types-core/src/interfaces.ts (1)
  • DynamicDatasourceInfo (10-24)
packages/types-core/src/global.ts (3)
packages/node-core/src/indexer/dynamic-ds.service.ts (2)
  • destroyDynamicDatasource (134-180)
  • getDynamicDatasources (183-193)
packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (2)
  • destroyDynamicDatasource (40-42)
  • getDynamicDatasources (44-46)
packages/types-core/src/interfaces.ts (2)
  • DynamicDatasourceDestructor (5-5)
  • DynamicDatasourceGetter (26-26)
🔇 Additional comments (13)
packages/node-core/src/indexer/indexer.manager.ts (1)

119-122: LGTM: Clean VM injection for template-based datasource queries.

The function correctly delegates to the dynamic datasource service to retrieve datasources by template name.

packages/types-core/src/global.ts (2)

5-5: LGTM: Import statement updated correctly.

Properly imports the new DynamicDatasourceDestructor and DynamicDatasourceGetter types.


15-16: LGTM: Global bindings for dynamic datasource lifecycle.

The new global functions align with the destructor and getter interfaces defined in interfaces.ts.

packages/node-core/src/indexer/dynamic-ds.service.spec.ts (2)

18-20: LGTM: Test helper updated for endBlock support.

The getTemplate method now correctly accepts an endBlock parameter, aligning with the production implementation.


74-457: Excellent test coverage for dynamic datasource destruction.

The test suite comprehensively covers:

  • Basic destruction scenarios
  • Error handling (non-existent, already-destroyed, out-of-bounds indices)
  • Template name validation
  • Global index tracking after destructions
  • Multi-template scenarios
  • EndBlock propagation and metadata persistence
  • In-memory state synchronization

This achieves the ≥80% coverage requirement from issue #2099.

packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (3)

6-21: LGTM: Worker-host interface extended correctly.

The HostDynamicDS interface and hostDynamicDsKeys array properly include the new destroyDynamicDatasource and getDynamicDatasourcesByTemplate bindings.


40-50: LGTM: Worker methods delegate properly to host.

Both destroyDynamicDatasource and getDynamicDatasourcesByTemplate correctly forward calls to the host implementation.


56-58: LGTM: Host function bindings complete.

The dynamicDsHostFunctions correctly binds the new service methods for cross-thread communication.

packages/node-core/src/indexer/dynamic-ds.service.ts (5)

106-122: LGTM: Clean implementation of template-based datasource queries.

The method correctly:

  • Filters by template name and active status (no endBlock)
  • Preserves global indices for destruction calls
  • Returns well-structured DynamicDatasourceInfo objects

130-132: LGTM: Simple and effective index-based lookup.

The optional chaining correctly returns undefined for out-of-bounds indices.


134-180: LGTM: Robust destruction implementation with excellent validation.

The method includes comprehensive checks:

  • Initialization state
  • Index bounds with clear error messages
  • Template name matching
  • Already-destroyed prevention
  • Array synchronization validation

The error messages are clear and actionable. The metadata persistence ensures durability across restarts.


210-217: LGTM: Template construction updated for endBlock support.

The method correctly:

  • Accepts optional endBlock parameter
  • Spreads it into the cloned template
  • Maintains the name removal to avoid pollution

219-229: LGTM: Datasource construction propagates endBlock correctly.

The endBlock is properly passed from params to getTemplate, ensuring consistency throughout the datasource lifecycle.

if (!this._datasources[index]) {
throw new Error(`Datasources array out of sync with params at index ${index}`);
}
(this._datasources[index] as any).endBlock = currentBlockHeight;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the any cast be removed here please

Comment on lines 124 to 144
// Inject function to destroy ds into vm
vm.freeze(async (templateName: string, index: number) => {
await this.dynamicDsService.destroyDynamicDatasource(templateName, blockHeight, index);

// Re-filter datasources to exclude the destroyed one
// The destroyed datasource now has endBlock set, so filterDataSources will exclude it
// Note: Reassigning filteredDataSources is intentional - subsequent handlers
// within the same block will see the updated filtered list
filteredDataSources = this.filterDataSources(blockHeight, filteredDataSources);
}, 'destroyDynamicDatasource');

Comment on lines 128 to 141
// Remove the destroyed datasource from the current processing array
// Find the datasource by matching the global index stored in the service
const destroyedDsParam = this.dynamicDsService.getDatasourceParamByIndex(index);
if (destroyedDsParam) {
const dsIndex = filteredDataSources.findIndex((fds) => {
return (
fds.startBlock === destroyedDsParam.startBlock &&
JSON.stringify((fds as any).options || (fds as any).processor?.options || {}) ===
JSON.stringify(destroyedDsParam.args || {})
);
});
if (dsIndex !== -1) {
filteredDataSources.splice(dsIndex, 1);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be moved into its own function.

const dsIndex = filteredDataSources.findIndex((fds) => {
return (
fds.startBlock === destroyedDsParam.startBlock &&
JSON.stringify((fds as any).options || (fds as any).processor?.options || {}) ===
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite fragile, there's no requirement that args maps to options or processor.options which would mean this fails. A better option would be to get the dynamic datasource rather than just the params and use that to compare.

The as any checks should also be removed.

@Gmin2 Gmin2 force-pushed the dynamic-datasource branch from d2a9845 to 87dca65 Compare December 1, 2025 22:02
@Gmin2
Copy link
Contributor Author

Gmin2 commented Dec 1, 2025

Take a look @stwiname

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
packages/node-core/src/indexer/indexer.manager.ts (1)

124-129: Destroyed datasource still processes remaining handlers in the same block.

Per the past review discussion with @stwiname, setting endBlock alone doesn't prevent the destroyed datasource from processing remaining handlers within the same block. The filterDataSources condition at line 154 uses >= nextProcessingHeight, so a datasource with endBlock === blockHeight is still included.

The comment indicates child classes should check ds.endBlock, but this requires coordination across all implementations. Consider:

  1. Removing the destroyed DS in-place from filteredDataSources using splice() after destruction, or
  2. Adding a test that confirms and documents the intended behavior (that destruction takes effect on the next block, not immediately)
#!/bin/bash
# Check if child implementations check endBlock before processing handlers
rg -nC5 'indexBlockData|endBlock' packages/node/src/indexer/ packages/node-core/src/indexer/ --type ts -g '!*.spec.ts' | head -80
🧹 Nitpick comments (3)
packages/node-core/src/indexer/dynamic-ds.service.ts (1)

176-178: Consider adding endBlock to BaseDataSource type for cleaner typing.

The Object.assign workaround is functional but the comment acknowledges the type mismatch. If BaseDataSource in @subql/types-core doesn't include endBlock, consider extending the type or using a union type to avoid runtime property assignment that bypasses TypeScript's type system.

For now, the implementation is correct - just a suggestion for future type safety improvements.

packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (1)

42-56: Consider serialization consistency for cross-thread communication.

createDynamicDatasource (line 39) uses JSON.parse(JSON.stringify(params)) for serialization over the worker bridge. The new methods don't apply the same pattern. While this may be fine if the worker thread uses a shared memory bridge or if the data is already serializable, consider whether getDatasourceParamByIndex should also ensure its return value is properly serialized.

If the bridge handles serialization automatically, this is fine as-is.

packages/node/src/indexer/indexer.manager.ts (1)

201-202: Add endBlock property to type definition for better type safety.

The type assertion (ds as { endBlock?: number }) at lines 201-202 bypasses TypeScript type checking. Since endBlock is being dynamically set and accessed in this lifecycle management feature, consider adding endBlock?: number to the SubstrateProjectDs type definition to maintain type safety throughout the codebase. Alternatively, use a type guard function instead of type assertion.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2a9845 and 87dca65.

📒 Files selected for processing (5)
  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts (6 hunks)
  • packages/node-core/src/indexer/dynamic-ds.service.ts (4 hunks)
  • packages/node-core/src/indexer/indexer.manager.ts (1 hunks)
  • packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (2 hunks)
  • packages/node/src/indexer/indexer.manager.ts (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript throughout with strict configuration in the monorepo
Use Ethers.js for Ethereum integration

Files:

  • packages/node/src/indexer/indexer.manager.ts
  • packages/node-core/src/indexer/indexer.manager.ts
  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts
  • packages/node-core/src/indexer/dynamic-ds.service.ts
  • packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Run ESLint with TypeScript support, Prettier, and various plugins across all TypeScript files using configured husky pre-commit hooks and lint-staged

Files:

  • packages/node/src/indexer/indexer.manager.ts
  • packages/node-core/src/indexer/indexer.manager.ts
  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts
  • packages/node-core/src/indexer/dynamic-ds.service.ts
  • packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use custom Jest configuration with module name mapping for workspace packages, enforce UTC timezone for tests, and handle Polkadot packages in transform ignore patterns

Files:

  • packages/node-core/src/indexer/dynamic-ds.service.spec.ts
🧬 Code graph analysis (3)
packages/node/src/indexer/indexer.manager.ts (2)
packages/node/src/indexer/types.ts (1)
  • isFullBlock (31-35)
packages/common-substrate/src/project/versioned/ProjectManifestVersioned.ts (1)
  • dataSources (54-56)
packages/node-core/src/indexer/dynamic-ds.service.spec.ts (1)
packages/node-core/src/indexer/dynamic-ds.service.ts (1)
  • DatasourceParams (18-23)
packages/node-core/src/indexer/dynamic-ds.service.ts (1)
packages/types-core/src/interfaces.ts (1)
  • DynamicDatasourceInfo (10-24)
🔇 Additional comments (13)
packages/node-core/src/indexer/indexer.manager.ts (1)

119-122: LGTM!

The getDynamicDatasources injection follows the established pattern and correctly delegates to the service method.

packages/node-core/src/indexer/dynamic-ds.service.spec.ts (5)

18-27: LGTM!

The test helper correctly mirrors the updated getTemplate signature, and testParamOther enables thorough multi-template testing.


74-136: Good test coverage for destruction scenarios.

The tests thoroughly verify:

  • EndBlock is correctly set on both params and datasource objects
  • Error handling for non-existent templates and already-destroyed datasources
  • Lifecycle flow of destroy-then-create

150-168: LGTM!

The test correctly verifies that resetDynamicDatasource preserves the endBlock on already-destroyed datasources while removing those created after the target height.


263-333: Comprehensive test coverage for getDynamicDatasourcesByTemplate.

The tests properly validate:

  • Global index preservation after destruction
  • Destroyed datasource exclusion
  • Args propagation
  • Initialization requirements

335-458: Thorough test coverage for index-based destruction.

The tests comprehensively cover:

  • Boundary validation (out of bounds, negative indices)
  • Global index semantics after partial destruction
  • Cross-template independence
  • Template name validation at specific indices
  • EndBlock propagation to both params and datasource objects
packages/node-core/src/indexer/dynamic-ds.service.ts (4)

98-122: LGTM!

Clean implementation that correctly filters active datasources by template and provides global indices for subsequent destruction operations.


124-132: LGTM!

Concise implementation with proper optional chaining.


213-219: LGTM!

Clean backward-compatible extension that includes endBlock in the datasource creation.


222-224: LGTM!

Correctly propagates endBlock from params during datasource reconstruction.

packages/node-core/src/indexer/worker/worker.dynamic-ds.service.ts (2)

9-23: LGTM!

The host interface and keys correctly mirror the new IDynamicDsService methods.


59-66: LGTM!

Host functions correctly bind all new service methods.

packages/node/src/indexer/indexer.manager.ts (1)

203-204: Clarify the destruction timing semantics.

The condition endBlock <= blockHeight will skip a datasource at the block where it's destroyed. The comment states "exclude it from processing in subsequent handlers within the same block," which aligns with this logic.

However, please verify the intended behavior:

  • When a datasource is destroyed during block N processing (e.g., in an early handler), should handlers that run LATER in the same block still process that datasource?
  • The current implementation will skip it for all handlers after destruction in the same block, which appears intentional but should be confirmed.

If the datasource should remain active for handlers executed BEFORE the destruction call within the same block, the current approach is correct. If it should be excluded entirely from block N when destroyed at block N, this is also correct. Please confirm this is the desired behavior and consider adding a test case that validates destruction timing within a single block.

Copy link
Collaborator

@stwiname stwiname left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't see a test that addresses this case. #2904 (comment)

Comment on lines +177 to +178
// BaseDataSource type doesn't include endBlock, but it exists at runtime
Object.assign(datasource, {endBlock: currentBlockHeight});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +198 to +205
// Skip datasources that have been destroyed at or before this block
// When a datasource is destroyed, its endBlock is set to the current blockHeight
// We want to exclude it from processing in subsequent handlers within the same block
const endBlock =
'endBlock' in ds ? (ds as { endBlock?: number }).endBlock : undefined;
if (endBlock !== undefined && endBlock <= blockHeight) {
continue;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bad place to do this, the provided dataSources should already have the filtered dataSources to be relevant.

Having this here makes it really hard to find as it's separate from the rest of the filtering

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[$300 in SQT] Dynamic Datasources destructor

2 participants